package main

import (
	"errors"
	"fmt"
	"math"
	"net"
	"os"
)

// 哨兵错误
var (
	ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
	ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
	ErrBufferFull        = errors.New("bufio: buffer full")
	ErrNegativeCount     = errors.New("bufio: negative count")
)

var ErrSentinel = errors.New("the underlying sentinel error")

type RuntimeError struct {
	e string
}

type DNSError struct {
}

func (e *DNSError) Error() string {
	return "DNSError error"
}

func (e *DNSError) Timeout() bool {
	return false
}
func (e *DNSError) Temporary() bool {
	return false
}

// 实现 error 接口
func (e *RuntimeError) Error() string {
	return e.e
}

type AreaError struct {
	err    string
	length float64
	width  float64
}

func (e *AreaError) Error() string {
	return e.err
}

func (e *AreaError) lengthNegative() bool {
	return e.length < 0
}

func (e *AreaError) widthNegative() bool {
	return e.width < 0
}

func main() {

	file, err := os.Open("test.txt")
	if err != nil {
		fmt.Println(err)
		return
	}
	// 根据f进行文件的读或写
	fmt.Println(file.Name(), "opened successfully")

	host, err := net.LookupHost("golangbot123.com")

	if err, ok := err.(*net.DNSError); ok {
		if err.Timeout() {
			fmt.Println("operation timed out")
		} else if err.Temporary() {
			fmt.Println("temporary error")
		} else {
			fmt.Println("generic error: ", err)
		}
		return
	}
	fmt.Println(host)

	fmt.Println("----------------------------------------")
	// 自定义错误
	radius := -20.0
	area, err := circleArea(radius)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("Area of circle %0.2f", area)

	fmt.Println("----------------------------------------")

	// 使用结构体处理错误
	length, width := -5.0, -9.0
	area, err = rectArea(length, width)
	if err != nil {
		if err, ok := err.(*AreaError); ok {
			if err.lengthNegative() {
				fmt.Printf("error: length %0.2f is less than zero\n", err.length)
			}
			if err.widthNegative() {
				fmt.Printf("error: width %0.2f is less than zero\n", err.width)
			}
		}
		fmt.Println(err)
		return
	}
	fmt.Println("area of rect", area)

	// errors.New和fmt.Errorf。使用这两种方法，可以轻松构造出一个满足 error 接口的错误值
	err01 := errors.New("your first demo error")
	errWithCtx := fmt.Errorf("index %d out of bounds", 10)

	fmt.Println(err01)
	fmt.Println(errWithCtx)

	fmt.Println("go-start: switch 错误分支判断---------------------------------")
	// 错误分支判断
	switch err01 {
	case ErrInvalidUnreadByte:
		fmt.Println("ErrInvalidUnreadByte --> " + err01.Error())
	case ErrInvalidUnreadRune:
		fmt.Println("ErrInvalidUnreadRune --> " + err01.Error())
	case ErrBufferFull:
		fmt.Println("ErrBufferFull --> " + err01.Error())
	case ErrNegativeCount:
		fmt.Println("ErrNegativeCount --> " + err01.Error())
	default:
		fmt.Println(err01.Error())
	}

	fmt.Println("go-end: switch 错误分支判断---------------------------------")

	//  error 类型变量的底层错误值是一个包装错误（Wrapped Error），errors.Is 方法会沿着该包装错误所在错误链（Error Chain)，
	// 与链上所有被包装的错误（Wrapped Error）进行比较，直至找到一个匹配的错误为止

	// 使用 %w 创建包装错误变量 err02,上层error为 ErrSentinel
	err02 := fmt.Errorf("wrap sentinal : %w", ErrSentinel)
	// 使用 %w 创建包装错误变量 err03,上层error为 err02
	err03 := fmt.Errorf("wrap err02 : %w", err02)

	// 通过比较操作符对 err03 与 ErrSentinel 进行比较后，我们发现这二者并不相同
	fmt.Println(err03 == ErrSentinel)
	// errors.Is 函数则会沿着 err03 所在错误链，向下找到被包装到最底层的“哨兵”错误值ErrSentinel
	if errors.Is(err03, ErrSentinel) {
		println("err03 is ErrSentinel")
		return
	}
	println("err03 is not ErrSentinel")

	// 自定义错误类型
	var myError = &RuntimeError{"RuntimeError error example"}
	myErr01 := fmt.Errorf("wrap err: %w", myError)
	myErr02 := fmt.Errorf("wrap myErr01: %w", myErr01)

	var e *RuntimeError
	// errors.As函数沿着 myErr02 所在错误链向下找到了被包装到最深处的错误值，并将 myErr02 与其类型 * RuntimeError。匹配成功后，errors.As 会将匹配到的错误值存储到 As 函数的第二个参数中
	// 尽量使用errors.As方法去检视某个错误值是否是某自定义错误类型的实例
	if errors.As(myErr02, &e) {
		println("RuntimeError is on the chain of myErr02")
		println(e == myError)
		return
	}
	println("RuntimeError is not on the chain of myErr02")
}

/* errors.New 只支持字符串的描述 */
func circleArea(radius float64) (float64, error) {
	if radius < 0 {
		return 0, errors.New("Area calculation failed, radius is less than zero")
	}
	return math.Pi * radius * radius, nil
}

/*
  使用Errorf向错误添加更多信息
*/
func circleArea02(radius float64) (float64, error) {
	if radius < 0 {
		return 0, fmt.Errorf("Area calculation failed, radius %0.2f is less than zero", radius)
	}
	return math.Pi * radius * radius, nil
}

/*面积计算函数*/
func rectArea(length, width float64) (float64, error) {
	err := ""
	if length < 0 {
		err += "length is less than zero"
	}
	if width < 0 {
		if err == "" {
			err = "width is less than zero"
		} else {
			err += ", width is less than zero"
		}
	}
	if err != "" {
		return 0, &AreaError{err, length, width}
	}

	return length * width, nil
}
